package com.bvbot.db;

import static com.bvbot.core.Logger.log;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import org.h2.tools.Server;

import com.bvbot.core.domain.DomainObject;

public class DbTool {

	private final static DbTool singleton = new DbTool();

	private static String IF_NAME = "192.168.30.5";

	private static final String DB_PORT = "6666";

	private Server server;

	private final ConcurrentHashMap<Class<?>, Object> domainSingletonsByClass = new ConcurrentHashMap<Class<?>, Object>();

	public static Connection getConnection() {
		Connection result = null;
		try {
			result = DriverManager.getConnection("jdbc:h2:tcp://" + IF_NAME
					+ ":" + DB_PORT + "/data/bvbot");
		} catch (SQLException e) {
			log("Error : impossible to get a db connection : " + e.getMessage(),
					e);
		}
		return result;
	}

	public static void startDb() throws SQLException {
		log("Starting H2 server ...");
		if (singleton.server != null) {
			throw new SQLException("Server is already running in this jvm");
		}
		File localFile = new File(".");
		String localPath = localFile.getAbsolutePath();
		singleton.server = Server.createTcpServer(new String[] { "-baseDir",
				localPath, "-tcpPort", DB_PORT });
		singleton.server.start();
		log("H2 server at " + singleton.server.getURL() + " is "
				+ singleton.server.getStatus());
		log("H2 server Started on port " + DB_PORT + ".");
	}

	public static void stopDb() throws SQLException {
		if (singleton.server == null) {
			throw new SQLException("No server is currently running in this jvm");
		}
		singleton.server.stop();
	}

	public static void helpDb() throws SQLException {
		Server.main(new String[] { "-?" });
	}

	public static void main(String[] args) throws Exception {
		if (args != null && args.length > 0) {
			if (args[0].equals("start")) {
				startDb();
			}
		} else
			helpDb();
	}

	public static void close(Connection conn) {
		if (conn != null) {
			try {
				if (!conn.isClosed()) {
					conn.close();
				}
			} catch (SQLException e) {
				log("Error : impossible to close the given connection : "
						+ conn + " : " + e.getMessage(), e);
			}
		}
	}

	public static void close(ResultSet rs) {
		if (rs != null) {
			try {
				if (!rs.isClosed()) {
					rs.close();
				}
			} catch (SQLException e) {
				log("Error : impossible to close the given ResultSet : " + rs
						+ " : " + e.getMessage(), e);
			}
		}
	}

	public static void close(Statement s) {
		if (s != null) {
			try {
				if (!s.isClosed()) {
					s.close();
				}
			} catch (SQLException e) {
				log("Error : impossible to close the given Statement : " + s
						+ " : " + e.getMessage(), e);
			}
		}
	}

	public static void execute(Connection conn, String string, boolean doLog) {
		if (conn != null && string != null) {
			try {
				Statement s = conn.createStatement();
				s.execute(string);
				if (doLog) {
					log("Execute sql : " + string);
				}
			} catch (SQLException e) {
				log("Impossible to execute sql " + string + " on connection "
						+ conn + " : " + e.getMessage(), e);
			}
		}
	}

	public static void executeSql(String string, boolean doLog) {
		Connection conn = null;
		Statement s = null;
		if (string != null) {
			try {
				conn = getConnection();
				s = conn.createStatement();
				if (doLog) {
					log("Execute sql : " + string);
				}
				s.execute(string);
			} catch (SQLException e) {
				log("Impossible to execute sql " + string + " on connection "
						+ conn + " : " + e.getMessage(), e);
			} finally {
				close(s);
				close(conn);
			}
		}
	}

	public static void toBase(DomainObject domainObject) throws IOException {
		domainObject.toDb(getConnection());
	}

	@SuppressWarnings({ "unchecked" })
	private static <T extends DomainObject> T getDomainSingletonInstance(
			Class<T> theClass) {
		T result = (T) singleton.domainSingletonsByClass.get(theClass);
		if (result == null) {
			try {
				result = theClass.newInstance();
			} catch (InstantiationException e) {
				log("Impossible to build a new instance of " + theClass + " : "
						+ e.getMessage(), e);
			} catch (IllegalAccessException e) {
				log("Impossible to build a new instance of " + theClass + " : "
						+ e.getMessage(), e);
			}
			singleton.domainSingletonsByClass.put(theClass, result);
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	public static <T extends DomainObject> List<T> fromDb(Class<T> theClass,
			String sql, boolean doLog) {
		List<T> result = new ArrayList<T>();
		Connection conn = null;
		Statement s = null;
		ResultSet rs = null;
		T singleton = getDomainSingletonInstance(theClass);
		try {
			conn = getConnection();
			s = conn.createStatement();
			if (doLog) {
				log("Execute query in fromDb method : " + sql);
			}
			rs = s.executeQuery(sql);
			while (rs.next()) {
				T instance = (T) singleton.fromDb(rs);
				result.add(instance);
			}
		} catch (Exception e) {
			log("Exception in fromDb method : " + e.getMessage(), e);
		} finally {
			close(rs);
			close(s);
			close(conn);
		}
		return result;
	}
}
